#pragma once
#include "export_dll.h"
#include "util_types.h"
#include <vector>
#include <memory>
#include <string>
#include <sstream>
#include "Exception.h"
#include "BaseObject.h"
#include "RefObject.h"

namespace yzrilyzr_array{
	template<typename T>
	class Array;
	typedef Array<int8_t> ByteArray;
}
namespace yzrilyzr_lang{
	class Object;
	class StringBuilder;

	enum StringEncoding{
		LOCAL, UTF8, UTF16
	};

	struct _StringData{
		std::vector<uint32_t> code_points;
		mutable bool hash_dirty=true;
		mutable int32_t cache_hashcode=0;
		mutable std::shared_ptr<std::string> cached_c_str=nullptr;
		mutable std::shared_ptr<std::wstring> cached_c_wstr=nullptr;
		mutable std::shared_ptr<std::u16string> cached_c_u16wstr=nullptr;
	};
	class U_EXPORT_API String :public BaseObject, public RefObject<_StringData>{
		private:
		//static std::vector<String> pool;
		_StringData * strData;
		protected:
		_StringData * refCopyOnWriteFrom()override{
			_StringData * newData=new _StringData();
			newData->code_points=strData->code_points;
			strData=newData;
			return newData;
		}
		void refDeleteInternal() override{
			delete strData;
			strData=nullptr;
		}
		void refCopyFrom(const RefObject<_StringData> & other) override{
			const String & source=static_cast<const String &>(other);
			strData=source.strData;
		}
		public:
		String() : strData(nullptr){}
		String(std::nullptr_t) : strData(nullptr){}
		~String(){
			refDoDestruct();
		}
		String(const String & copy){
			strData=nullptr;
			refDoCopy(copy);
		}
		String(const String & src, u_index offset, u_index length){
			if(offset > src.length() || offset + length > src.length()){
				throw IndexOutOfBoundsException("StringIndexOutOfBoundsException");
			}
			strData=new _StringData();
			refDoConstruct(strData);
			strData->code_points.assign(src.strData->code_points.begin() + offset,
										src.strData->code_points.begin() + offset + length);
		}
		String(const std::vector<uint32_t> & code_points) :String(code_points, 0, code_points.size()){}
		String(const std::vector<uint32_t> & code_points, u_index offset, u_index length){
			if(offset > code_points.size() || offset + length > code_points.size()){
				throw IndexOutOfBoundsException("StringIndexOutOfBoundsException");
			}
			strData=new _StringData();
			refDoConstruct(strData);
			strData->code_points.assign(code_points.begin() + offset,
										code_points.begin() + offset + length);
		}

		String(const char value){
			strData=new _StringData();
			refDoConstruct(strData);
			strData->code_points.push_back(static_cast<uint32_t>(value));
		}
		String(const bool value) : String(value?"true":"false"){}
		String(const float value) : String(std::to_string(value)){}
		String(const double value) : String(std::to_string(value)){}
		String(const int32_t value) : String(std::to_string(value)){}
		String(const uint32_t value) : String(std::to_string(value)){}
		String(const long value) : String(std::to_string(value)){}
		String(const long long value) : String(std::to_string(value)){}
		String(const uint64_t value) : String(std::to_string(value)){}
		String(const Object & value);
		String(const std::string & str, StringEncoding encoding=LOCAL);
		String(const std::wstring & str);
		String(const std::u16string & str);

		String(const char * str, StringEncoding encoding=LOCAL);
		String(const wchar_t * str);
		String(const char16_t * str);
		String(const wchar_t * str, u_index off, u_index len);
		String(const char16_t * str, u_index off, u_index len);

		String(const yzrilyzr_array::ByteArray & str, StringEncoding encoding=LOCAL);
		String(const yzrilyzr_array::ByteArray & str, u_index off, u_index len, StringEncoding encoding=LOCAL);

		const std::string tostring(StringEncoding encoding=LOCAL) const;
		const std::wstring towstring() const;
		const std::u16string tou16string() const;
		yzrilyzr_array::ByteArray getBytes(StringEncoding encoding=LOCAL)const;
		private:
		static bool is_local_encoding_utf8();
		//
		void byte_to_codepoint(const char * src, u_index len, StringEncoding srcEncoding=LOCAL);
		//
		const std::string codepoint_to_utf8()const;
		void utf8_to_codepoint(const char * paramRegPtr, u_index len);
		//
		const std::wstring codepoint_to_utf16w()const;
		void utf16w_to_codepoint(const wchar_t * paramRegPtr, u_index len);
		//
		const std::u16string codepoint_to_utf16()const;
		void utf16_to_codepoint(const char16_t * paramRegPtr, u_index len);
		//
		const std::string local_to_utf8(const char * paramRegPtr, u_index len)const;
		const std::string utf8_to_local(const std::string & utf8_str)const;

		void checkIndex(u_index index)const{
			if(index < 0){
				throw IndexOutOfBoundsException("index < 0");
			}
			if(index >= length()){
				std::stringstream ss;
				ss << "index (" << index << ") >= length (" << length() << ")";
				throw IndexOutOfBoundsException(ss.str());
			}
		}
		void invalidateCache(){
			strData->hash_dirty=true;
			strData->cached_c_str.reset();
			strData->cached_c_wstr.reset();
			strData->cached_c_u16wstr.reset();
		}
		public:
		u_index length() const{
			if(strData == nullptr)throw NullPointerException("Call length() on null String");
			return strData->code_points.size();
		}

		uint32_t codePointAt(u_index index) const{
			checkIndex(index);
			return strData->code_points[index];
		}

		String substring(u_index startInclusive, u_index endExclusive) const{
			if(startInclusive > endExclusive || endExclusive > length()){
				throw IndexOutOfBoundsException("StringIndexOutOfBoundsException");
			}
			return String(*this, startInclusive, endExclusive - startInclusive);
		}

		String substring(u_index startInclusive) const{
			return substring(startInclusive, length());
		}

		String trim() const;
		String clone()const{
			return String(*this, 0, length());
		}

		String & operator=(const String & str){
			refDoAssignCopy(str);
			return *this;
		}

		String operator+(const String & str) const{
			return clone()+=str;
		}
		String operator*(u_index count)const{
			return clone()*=count;
		}
		String & operator+=(const String & str);
		String & operator*=(u_index count);
		String & operator<<(const String & str){
			return *this+=str;
		}

		bool operator==(const std::nullptr_t n) const{
			return strData == nullptr;
		}

		bool operator==(const String & str) const{
			return strData->code_points == str.strData->code_points;
		}

		bool operator!=(const String & str) const{
			return !(*this == str);
		}

		std::vector<uint32_t>::iterator begin()const{
			return strData->code_points.begin();
		}
		std::vector<uint32_t>::iterator end()const{
			return strData->code_points.end();
		}

		uint32_t operator[](u_index index)const{
			checkIndex(index);
			return strData->code_points[index];
		}

		char16_t charAt(u_index index)const{
			return (*this)[index];
		}
		bool contains(const String & s) const{
			return indexOf(s) >= 0;
		}

		bool startsWith(const String & prefix) const{
			return startsWith(prefix, 0);
		}

		bool startsWith(const String & prefix, u_index offset) const{
			if(offset < 0 || static_cast<u_index>(offset) > length() - prefix.length()){
				return false;
			}
			for(u_index i=0; i < prefix.length(); ++i){
				if((*this)[offset + i] != prefix[i]){
					return false;
				}
			}
			return true;
		}

		bool endsWith(const String & suffix) const{
			return startsWith(suffix, length() - suffix.length());
		}

		String concat(const String & s) const{
			return this->clone() + s;
		}

		u_index indexOf(const String & s) const{
			return indexOf(s, 0);
		}

		u_index indexOf(const String & s, u_index offset) const;

		u_index lastIndexOf(const String & str) const{
			return lastIndexOf(str, length());
		}

		u_index lastIndexOf(const String & str, u_index fromIndex) const;

		String replace(const String & oldStr, const String & newStr) const;

		yzrilyzr_array::Array<String> split(const String & delimiter) const;

		int32_t hashCode() const override{
			if(!strData->hash_dirty && strData->cache_hashcode != 0) return strData->cache_hashcode;
			int32_t hash=0;
			for(uint32_t cp:strData->code_points){
				hash=31 * hash + cp;
			}
			strData->cache_hashcode=hash;
			strData->hash_dirty=false;
			return hash;
		}

		String toString() const override{
			return *this;
		}
		const char * c_str(StringEncoding encoding=LOCAL) const{
			if(!strData->cached_c_str){
				strData->cached_c_str=std::make_shared<std::string>(tostring(encoding));
			}
			return strData->cached_c_str->c_str();
		}
		const wchar_t * c_wstr() const{
			if(!strData->cached_c_wstr){
				strData->cached_c_wstr=std::make_shared<std::wstring>(towstring());
			}
			return strData->cached_c_wstr->c_str();
		}
		const char16_t * c_u16str() const{
			if(!strData->cached_c_u16wstr){
				strData->cached_c_u16wstr=std::make_shared<std::u16string>(tou16string());
			}
			return strData->cached_c_u16wstr->c_str();
		}

		bool empty() const{
			return strData == nullptr || length() == 0;
		}

		bool isEmpty() const{
			return strData == nullptr || length() == 0;
		}
		String toUpperCase()const;
		String toLowerCase()const;
		int32_t operator<(const String & o)const{
			auto & oth=o.strData->code_points;
			u_index len1=length();
			u_index len2=o.length();
			for(u_index i=0, j=(len1 < len2?len1:len2);i < j;i++){
				auto cp1=strData->code_points[i];
				auto cp2=oth[i];
				if(cp1 != cp2){
					return cp1 < cp2;
				}
			}
			return len1 < len2;
		}

		friend std::istream & operator>>(std::istream & is, String & str){
			std::string temp;
			is >> temp;
			str=String(temp);
			return is;
		}

		friend std::ostream & operator<<(std::ostream & os, const String & str){
			os << str.tostring();
			return os;
		}

		friend String operator+(const char * l, const String & r){
			return String(l) + r;
		}

		std::string getDebugString() const{
			try{
				return tostring(LOCAL);
			} catch(...){
				return "[Error]";
			}
		}
	};
}
template
class U_EXPORT_API std::vector<yzrilyzr_lang::String>;
namespace std{
	template<>
	struct hash<yzrilyzr_lang::String>{
		size_t operator()(const yzrilyzr_lang::String & obj) const noexcept{
			return static_cast<size_t>(obj.hashCode());
		}
	};

	template<>
	struct hash<yzrilyzr_lang::String *>{
		size_t operator()(const yzrilyzr_lang::String * obj) const noexcept{
			return obj?static_cast<size_t>(obj->hashCode()):0;
		}
	};

	template<>
	struct hash<std::shared_ptr<yzrilyzr_lang::String>>{
		size_t operator()(const std::shared_ptr<yzrilyzr_lang::String> & obj) const noexcept{
			return obj?static_cast<size_t>(obj->hashCode()):0;
		}
	};

	template<>
	struct hash<std::unique_ptr<yzrilyzr_lang::String>>{
		size_t operator()(const std::unique_ptr<yzrilyzr_lang::String> & obj) const noexcept{
			return obj?static_cast<size_t>(obj->hashCode()):0;
		}
	};
}